﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Threading;
using System.Collections.Concurrent;
using HtmlAgilityPack;
using System.Text;

namespace SimilarTextWebCrawlerWCFService
{
    public class CrawlerThread
    {
        private const int NUMBER_OF_WORDS_TO_RETURN = 10;

        private const int MAX_BREDTH = 5;

        private ManualResetEvent _doneEvent;

        private Uri _starting_location;

        public Dictionary<string, int> WordsAndWeights { get { return _words_and_weights; } }
        private Dictionary<string, int> _words_and_weights;

        private string _word_to_find;

        public CrawlerThread(string word, Uri location, ManualResetEvent doneEvent)
        {
            _word_to_find = word;
            _starting_location = location;
            _doneEvent = doneEvent;
            _words_and_weights = new Dictionary<string, int>();
        }

        // Wrapper method for use with thread pool.
        public void ThreadPoolCallback(Object depth)
        {
            int _max_depth = (int)depth;
            getWhatWasMent(_word_to_find, _starting_location, 0, _max_depth);
        }

        private void getWhatWasMent(string word, Uri location, int currentDepth, int maxDepth)
        {
            string[] relatedWords = getRelatedWords(_word_to_find, _starting_location);
            var builder = new StringBuilder();
            Array.ForEach(relatedWords, x => builder.Append(x).Append(","));
            Console.WriteLine(word + " has related words: " + builder.ToString());
            updateOverallWordsAndWeights(ref relatedWords);
        }

        private void recurse(string word, Uri location, int currentDepth, int maxDepth)
        {
            List<Uri> links = getLinks(location);
            int max = links.Count() > MAX_BREDTH ? MAX_BREDTH : links.Count();
            for (int i = 0; i < max; i++)
            {
                if (currentDepth + 1 != maxDepth
                    && currentDepth != maxDepth)
                {
                    getWhatWasMent(word, links[i], currentDepth + 1, maxDepth);
                }
                else
                {
                    break;
                }
            }
        }

        private void updateOverallWordsAndWeights(ref string[] relatedWords)
        {
            for (int i = 0; i < relatedWords.Length; i++)
            {
                addOrIncrement(ref _words_and_weights, relatedWords[i], i);
            }
        }

        /// <summary>
        /// Add the key and value pair to the dictionary or increment the values
        /// that are already in the dictionary.
        /// </summary>
        /// <typeparam name="T">Any type for the key</typeparam>
        /// <param name="dictionary">A reference to a dictionary.</param>
        /// <param name="key"></param>
        /// <param name="value"></param>
        private void addOrIncrement<T>(ref Dictionary<T, int> dictionary, T key, int value)
        {
            if (dictionary.ContainsKey(key))
            {
                dictionary[key] = dictionary[key] + value;
            }
            else
            {
                dictionary.Add(key, value);
            }
        }

        /// <summary>
        /// Get words on the specified webpage that are related to the one
        /// requested.
        /// </summary>
        /// <param name="word"></param>
        /// <param name="page"></param>
        /// <returns></returns>
        public static string[] getRelatedWords(string word, Uri page)
        {
            RelatedWordsWCFService.RelatedWordsWCFServiceClient client =
                new RelatedWordsWCFService.RelatedWordsWCFServiceClient();
            string[] relatedWords = client.getRelatedWordsFromAddress(word, page);
            client.Close();
            return relatedWords;
        }

        /// <summary>
        /// Get the links from a webpage.
        /// </summary>
        /// <param name="page">Webpage URL</param>
        /// <returns>A list of links on the webpage.</returns>
        private List<Uri> getLinks(Uri page)
        {
            List<Uri> linksOnPage = new List<Uri>();
            HtmlWeb hw = new HtmlWeb();
            HtmlDocument doc = hw.Load(page.ToString());
            foreach (HtmlNode link in doc.DocumentNode.SelectNodes("//a[@href]"))
            {
                string href_val = link.GetAttributeValue("href", "");
                try
                {
                    linksOnPage.Add(new Uri(href_val));
                }
                catch (UriFormatException ufe)
                {
                    //Incorrectly formatted url.
                }
                if (linksOnPage.Count > MAX_BREDTH)
                {
                    break;
                }
            }
            return linksOnPage;
        }
    }
}
